#!/usr/bin/python -S
"""
cre_parse.py -- Parser for CRE langauge.

The two composites are alternation and sequences.

choice of sequences:

  either 'a' Foo or 'b' Bar or 'c' Spam
  either ('a' Foo) or ('b' Bar) or ('c' Spam)

sequence of choices
  either 'a' or 'b' either 'c' or 'd'
  (either 'a' or 'b') (either 'c' or 'd')


"""

import util


def NegatableInClass(p):
  """    NegatableInClass <- '!'? (PERL / POSIX / UNICODE)
        # Examples:  word  !digit  :alnum  !:space  ::Greek  !::Greek
  """
  # NOTE: can't negate the following inside a character class.  You can only
  # negate the entire class.
  #   space  -- synonym for ' '
  #   backslash -- synonym for '\\'
  #   any --
  return Negatable(p)  # Identical body


def Negatable(p):
  "    Negatable <- '!'? (Class / Assert / PERL / POSIX / UNICODE)    "
  # This is for things that are negatable in the normal context (outside a
  # character class.)
  #
  # NOTE: It's possible you could do !space by turning into !chars[space] or
  # [^ ], but for now we're now doing that.
  tilde, (unused_index, expr) = p
  if tilde:
    return expr + [False]
  else:
    return expr + [True]


def Class(p):
  "    Class <- 'chars' '[' ClassItem+ ']'    "
  _, _, items, _ = p
  return ['Class'] + items


def Char(p):
  """
    Char <- CHAR / CHAR_LITERAL / SINGLE

    # Examples:  a  0x00  &201c  &space
  """
  # Ways to write a single, specific, character (not a metacharacter).
  _, expr = p
  return expr

  # A way to write a character


def ClassItem(p):
  "    ClassItem <- Char '-' Char / Char / CLUMP / NegatableInClass    "
  index, expr = p
  if index == 0:
    start, _, end = expr
    return ['Range', start, end]
  elif index == 1:  # char
    return expr
  elif index == 2:
    return expr
  elif index == 3:
    return expr
  else:
    raise AssertionError


# TODO: what about \n OUTSIDE of strings?  or is that just 'newline'?  Or just
# have people type '\n' ?
def Atom(p):
  "    Atom <- 'any' / LITERAL / SINGLE / ZERO  "
  # An atom is everything that can't have any children.

  _, token = p
  return token


def Primary(p):
  """
    Primary <- Atom
             / Negatable
             / IDENTIFIER ! '='
             / Group
             / Capture
             / Choice
             / BackRef
  """
  index, expr = p
  if index in (0, 1):
    return expr
  elif index == 2:
    # Pick out the identifier.
    assert isinstance(expr, list), expr
    assert len(expr) == 1, expr
    return expr[0]
  elif 3 <= index <= 7:
    return expr
  else:
    raise AssertionError


def BackRef(p):
  "    BackRef <- 'REF' '(' (DIGIT / IDENTIFIER) ')'   "
  _, _, choice, _ = p
  index, expr = choice
  if index == 0:
		ref = expr  # digit is itself
  elif index == 1:
		_, ref = expr
  else:
		raise AssertionError
  return ['BackRef', ref]


def Assert(p):
  "    Assert <- ASSERT '(' Sequence ')'   "
  assert_t, _, expr, _ = p
  _, left, _ = assert_t
  # turn 'left' -> True, '' -> False
  # Since assert is negatable, full assert looks like:
  # ['Assert', 'a', False, True]
  return ['Assert', expr, bool(left)]


# NOTE: In Perl (and others), you can have a non-capturing group with flags.
# That will set flags for stuff in taht group only.  In Python it has to be at
# the front of the entire expression.
#
# It would look like this:
#
# (flags(ignorecase) 'a' wordchar+)
#
# The parse tree would be something like ['Group', ['Flags', 'ignorecase'],
# ['Literal', 'a']]

def Group(p):
  "    Group <- '(' Sequence ')'   "
  _, expr, _ = p
  return ['Group', expr]


def Capture(p):
  "    Capture <- '{' Sequence ('as' IDENTIFIER)? '}'    "
  assert len(p) == 4, p
  _, expr, optional, _ = p
  if optional:
    _, ident = optional
    # This looks like ['Ref', 'age']
    _, name = ident
  else:
    name = None
  return ['Capture', name, expr]


def SimpleRepeat(p):
  """
    SimpleRepeat <- '?' / '*' / '+'
                  / '??' / '**' / '++'
  """
  index, expr = p
  if index == 0:  # ?
    return (None, '?')
  elif index == 1:  # *
    return (None, '*')
  elif index == 2:  # +
    return (None, '+')
  elif index == 3:  # ??
    return ('N', '?')
  elif index == 4:  # **
    return ('N', '*')
  elif index == 5:  # ++
    return ('N', '+')
  else:
    raise AssertionError


def RepeatRange(p):
  "    RepeatRange <- DIGIT? '..' DIGIT?   "
  left, op, right = p
  if not left:  # [] -> None
    left = None
  if not right:
    right = None
  return (left, right)


def RepeatSpec(p):
  """
    RepeatSpec <- '(' MODIFIER?
                  ( RepeatRange / DIGIT / '?' / '*' / '+' )
                  ')'
  """
  _, modifier, repeat, _ = p

  if modifier:
    _, m, _ = modifier
  else:
    m = None

  index, expr = repeat
  if index == 0:  # 3..4
    return (m, expr)
  elif index == 1:  # digit
    digit = expr
    # Return a range of just that digit
    return (m, digit)

  # Turn these operators into ranges.  Range items are strings or None.
  elif index == 2:  # ?
    return (m, '?')
  elif index == 3:  # *
    return (m, '*')
  elif index == 4:  # +
    return (m, '+')
  else:
    raise AssertionError


def Suffix(p):
  "    Suffix <- Primary ( SimpleRepeat / '^' DIGIT / '^' RepeatSpec )?    "
  expr, suffix = p
  if suffix == []:
    return expr

  index, subexpr = suffix
  if index == 0:
    repeat_spec = subexpr
  elif index == 1:
    # A single digit is like {3,3}
    _, d = subexpr
    repeat_spec = (None, d)
  elif index == 2:
    _, repeat_spec = subexpr
  else:
    raise AssertionError

  # ['Repeat', 'N', '0', None, ['Choice' ...]]
  return ['Repeat'] + list(repeat_spec) + [expr]


def Sequence(p):
  '    Sequence <- Suffix+   '
  # Collapse singleton sequences.
  if len(p) == 1:
    #print '1. CRE Sequence returning', p[0]
    return p[0]

  #print '2. CRE Sequence returning', ['Seq'] + p
  return ['Seq'] + p


# NOTE: This differs from the TPE Choice -- there's no singleton.  You need at
# least two choices to match this.

def Choice(p):
  "    Choice <- 'either' Sequence ('or' Sequence)+    "

  assert len(p) == 3, p
  #print '\tChoice', p, len(p)

  keyword, first, rest = p

  # Flattens second part
  choices = [first]
  #print 'Choice first', first
  # print 'Choice rest', rest
  for c in rest:
    assert len(c) == 2, c
    choices.append(c[1])

  return ['Choice'] + choices


# In TPE, we use <- for rules, because we might have ->.  I don't think we will
# have a RHS, so use =
def Definition(p):
  "    Definition <- IDENTIFIER '=' Sequence   "
  assert len(p) == 3, p
  name, _, value = p
  # TODO: This is the LHS.  Fix IDENTIFIER to return Identifier, and then change
  # it to Ref on the RHS?
  assert name[0] == 'Ref', name
  return ['Definition', name[1], value]


# Actions for each definition.
def Flags(p):
  # Flags      <- FLAGS identifier* ;
  # end with ; ?  newline?  that isn't a token now.
  "    Flags <- 'flags' '(' IDENTIFIER* ')'    "
  _, _, identifiers, _ = p
  result = []
  for _, name in identifiers:
    result.append(name)
  return ['Flags'] + result


def Start(p):
  '    Start <- Flags? (Sequence / Definition+) !.    '
  # A CRE can stand alone, or it can be a list of named subexpressions.
  # It can be prefixed with flags.
  flags, (index, expr) = p
  if index == 0:
    defs = [['Definition', 'Start', expr]]
  elif index == 1:
    defs = expr
  else:
    raise AssertionError(index)

  # Flags, then a LIST of definitions.
  return ['Cre', flags, defs]


# TODO: Simplify these?
# Could we have a RHS that fixes this?

def IDENTIFIER(p):
  # An identifier on the LHS or RHS.
  _, name, _ = p
  return ['Ref', name]


def SINGLE(p):
  _, name, _ = p
  return ['Single', name]


def CLASS(p):
  _, name, _ = p
  return ['Class', name]


def PERL(p):
  _, name, _ = p
  return ['Perl', name]


def POSIX(p):
  _, name, _ = p
  return ['Posix', name]


def UNICODE(p):
  _, name, _ = p
  return ['Unicode', name]


def LITERAL(p):
  _, value, _ = p
  return ['Literal', value]


def ZERO(p):
  _, name, _ = p
  return ['Zero', name]


def CHAR(p):
  _, value, _ = p
  return value


def CLUMP(p):
  # Just like CHAR, but not valid in a char range.
  _, chars, _ = p
  return chars


def DIGIT(p):
  _, value, _ = p
  return value


def CHAR_LITERAL(p):
  # The value is a Python string, e.g. u'\u201c'
  _, value, _ = p
  return value


def any(p):
  _, value, _ = p
  return ['Single', 'any']


_parser = None

def GetParser():
  # TODO: break dependency?
  import tpe_compile

  global _parser
  if _parser:
    return _parser

  rules, actions = util.get_rules_and_actions(__name__, arg_name='p')
  _parser = tpe_compile.Parser(rules, actions)
  return _parser

if __name__ == '__main__':
  # Print out the parser
  rules, actions = util.get_rules_and_actions(__name__, arg_name='p')
  print rules
